//--------------------- headers ----------------------//

#include <iostream>
#include <cstdlib>
#include <signal.h>
#include "RtMidi.h"
#include <cmath>
#include "Features.h"
#include "GrooveHeader.h"
#include "OscInterface.h"
#include "GrooveInfo.h"
#include <time.h>



//------------ No need to care about this part -------------------//
#if defined(__WINDOWS_MM__)
#include <windows.h>
#define SLEEP( milliseconds ) Sleep( (DWORD) milliseconds ) 
#else // Unix variants
#include <unistd.h>
#define SLEEP( milliseconds ) usleep( (unsigned long) (milliseconds * 1000.0) )
#endif
bool done;
static void finish( int ignore ){ done = true; }
void usage( void ) {
	// Error function in case of incorrect command-line
	// argument specifications.
	std::cout << "\nusage: qmidiin <port>\n";
	std::cout << "    where port = the device to use (default = 0).\n\n";
	exit( 0 );
}


//-------------- global variables ------------------//
#define PORT 2 // depending on what device to use
#define VIRTUAL 0 // 0 for using real device, 1 for using software virtual device
// maximum number of symbols in a measure
#define MAX_IOI 1.0/16.0 // we don't allow two consecutive same note to sound in a threshold of time duration (wrt a measure)
#define NUM_PLAYERS 5  // just for now
#define NUM_STROKES 4
int midinotes[NUM_PLAYERS][MAXNUM_MIDI];
double positions[NUM_PLAYERS][MAXNUM_MIDI];
int velocities[NUM_PLAYERS][MAXNUM_MIDI];
int measure_period = 2000; // in msec
int velocityDecay = 20; // threshold for velocity 
// A few more global variables
Groove gTemplates[NUM_GROOVES];
int numMeasuresSofar=0;
int Label[2000];
// Tree variables
srch srchparams;
prob var;
pstNode node, node1;
pstNode PST;
pstData data;
// Predictive distribution
float probab[NUM_GROOVES];
float grPredDist[NUM_GROOVES];
int grPredIndex[NUM_GROOVES];
int predMAPGroove;
int predStochasticGroove;

int currDummy = 2;
int prevDummy = 1;

//------------------ customized functions ----------------------//
// clear the global variables, called after all calculation is done for a measure
void clear_arrays() {
	// clear midinotes and IOIs
	
	for (int i=0; i<NUM_PLAYERS; i++) {
		for (int j=0; j<MAXNUM_MIDI; j++) {
			midinotes[i][j] = -1;
			positions[i][j] = -1;
			velocities[i][j] = -1;
		}
		
	}
}


//----------------- when a measure ends, the function is called --------------------//
void measure_end(FILE *f_raw) {
	Groove *gTlptr = (Groove*)(&gTemplates[0]);
	
	// look at all players as one stream
	int midinotesAll[MAXNUM_MIDI];
	double velocitiesAll[MAXNUM_MIDI];
	double positionsAll[MAXNUM_MIDI];
	double IOIsAll[MAXNUM_MIDI];
	int numAll = 0;
	
	for (int j=0; j<MAXNUM_MIDI; j++) {
		double positionMin = 2; // init
		int pMin = -1;
		int iMin = -1;
		for (int p=0; p<NUM_PLAYERS; p++) {
			for (int i=0; i<MAXNUM_MIDI; i++) {
				if (positions[p][i] == -1) {
					break;
				}
				else if (positions[p][i] < positionMin) {
					positionMin = positions[p][i];
					pMin = p;
					iMin = i;	
					positions[pMin][iMin] = 3; // make it impossible to reach twice
				}
			}
		}
		
		if (pMin == -1) {
			midinotesAll[j] = -1;
			velocitiesAll[j] = -1;
			positionsAll[j] = -1;
			numAll = j;
			break;
		}
		else {
			
			midinotesAll[j] = midinotes[pMin][iMin];
			velocitiesAll[j] = velocities[pMin][iMin];
			positionsAll[j] = positionMin;
		}		
	}
	
	positionsAll[numAll] = 1;
	for (int i=0; i<numAll; i++) {
		
		IOIsAll[i] = positionsAll[i+1] - positionsAll[i];
		IOIsAll[i] = round(IOIsAll[i] * 32); // quantize to 32th note
		
		fprintf(f_raw, "%d\t%f\t%f\n", midinotesAll[i], velocitiesAll[i], IOIsAll[i]);
	}
	
	
//	for (int i=0; i<numAll; i++) {
//		std::cout << midinotesAll[i]<< '\t' << IOIsAll[i] << std::endl;
//	}	
	
	/*	
	 // look at each player
	 for (int p=0; p<NUM_PLAYERS; p++) {
	 
	 std::cout << "from player " << p << std::endl;
	 // do some analysis
	 double IOIs[MAXNUM_MIDI]; // quantized to 16-th notes
	 int noteNum = 0;
	 
	 if (positions[p][0] == -1) {
	 // means empty in this measure!
	 }
	 else {
	 for (int i=0; i<MAXNUM_MIDI; i++) {
	 //std::cout << positions[p][i] << std::endl;
	 if (positions[p][i] == -1) {
	 positions[p][i] = 1; // make loops easier
	 noteNum = i;
	 break;
	 }
	 }
	 
	 for (int i=0; i<noteNum; i++) {
	 IOIs[i] = positions[p][i+1] - positions[p][i];
	 IOIs[i] = round(IOIs[i] * 32); // quantize to 32th note
	 }
	 Groove grooveNow;
	 int i;
	 for (i=0; i<noteNum; i++) {
	 //std::cout << "midinote " << midinotes[p][i] << "\tIOI " << IOIs[i] << std::endl;
	 grooveNow.note[i] = (char)(getNoteName(midinotes[p][i] - 12*p));
	 grooveNow.dur[i] = (char)(getDurName(IOIs[i]));
	 }
	 grooveNow.length = i;
	 grooveNow.note[i] = '\0';
	 grooveNow.dur[i] = '\0';
	 
	 Groove *gptr = (Groove*)(&grooveNow);
	 Label[p][numMeasuresSofar] = GrooveClassify(gptr,gTlptr,NUM_GROOVE_TEMPLATES);
	 printf("Player %d: Groove - %d\n",p,Label[p][numMeasuresSofar]);
	 PST[p] = buildPSTRealTime(PST[p], &(Label[p][0]), findmax(0,numMeasuresSofar-DMAX), numMeasuresSofar);
	 getPredSymbDist(PST[p], &(Label[p][0]), findmax(0,numMeasuresSofar-DMAX), numMeasuresSofar, NUM_GROOVE_TEMPLATES, probab);
	 printf("Predictive Distribution for player %d:\n",p);
	 for(i=0;i<NUM_GROOVE_TEMPLATES;i++){
	 printf("%d:%.2f\n",i+1,probab[i]);
	 }
	 }
	 }
	 */
	
	if (numAll > 0) {
		Groove grooveNow;
		int i;
		for (i=0; i<numAll; i++) {
			//std::cout << "midinote " << midinotes[p][i] << "\tIOI " << IOIs[i] << std::endl;
			grooveNow.note[i] = (char)(getNoteName(midinotesAll[i]));
			grooveNow.dur[i] = (char)(getDurName(IOIsAll[i]));
		}
		grooveNow.length = i;
		grooveNow.note[i] = '\0';
		grooveNow.dur[i] = '\0';
		
		Groove *gptr = (Groove*)(&grooveNow);
		Label[numMeasuresSofar] = GrooveClassify(gptr,gTlptr,NUM_GROOVE_TEMPLATES);
		
		PST = buildPSTRealTime(PST, &(Label[0]), findmax(0,numMeasuresSofar-DMAX), numMeasuresSofar);
		getPredSymbDist(PST, &(Label[0]), findmax(0,numMeasuresSofar-DMAX), numMeasuresSofar, NUM_GROOVE_TEMPLATES, grPredDist);
		  
		// Groove sorting and prediction
		for(i=0;i<NUM_GROOVE_TEMPLATES;i++){
			grPredIndex[i] = i;
		}
		grooveSort(NUM_GROOVE_TEMPLATES, &(grPredDist[0]), grPredIndex);
		predMAPGroove = grPredIndex[0]+1;
		predStochasticGroove = grooveSample(NUM_GROOVE_TEMPLATES, &(grPredDist[0]), grPredIndex);
//		printf("Sorted Predictive Distribution for player\n");
		for(i=0;i<NUM_GROOVE_TEMPLATES;i++){
//			printf("Groove %d:%.2f\n",grPredIndex[i]+1,grPredDist[i]);
		}
		printf("Current Groove - %d\n",Label[numMeasuresSofar]);
		printf("MAP Predicted Groove: %d\n",predMAPGroove);
		printf("Stochastically predicted Groove: %d\n",predStochasticGroove);
		//send osc data
		OscInterface *osc = new OscInterface();
		GrooveInfo *gi = new GrooveInfo();
		
		
		//gi->currentGroove = currDummy++;
        //gi->predGroove = prevDummy++;
		
		//currDummy %= NUM_GROOVE_TEMPLATES + 1;
		//prevDummy %= NUM_GROOVE_TEMPLATES + 1;
		
		gi->currentGroove = Label[numMeasuresSofar];
        gi->predGroove = predMAPGroove;
        
        for (int i = 0; i < NUM_GROOVE_TEMPLATES; ++i) {
            gi->predDist[i] = grPredDist[i];
        }
		osc->sendOscData(gi);
		
		fprintf(f_raw,"Current Groove - %d\n",Label[numMeasuresSofar]);
		fprintf(f_raw, "MAP Predicted Groove: %d\n",predMAPGroove);
		fprintf(f_raw, "Stochastic Predicted Groove: %d\n",predStochasticGroove); 
		fprintf(f_raw, "%d\t256\t256\n", numMeasuresSofar);
		
		numMeasuresSofar++;
		

		
		fflush(f_raw);
	}
	
	std::cout << "measure " << numMeasuresSofar << " ends" << std::endl;
}

void measure_end_FAKE()
{
	// FAKE data
	Label[numMeasuresSofar] = 3;
	predMAPGroove = 4;
	for (int i = 0; i < NUM_GROOVE_TEMPLATES; ++i) {
		grPredDist[i] = 1.0/NUM_GROOVE_TEMPLATES;
	}
	
	//send osc data
	OscInterface *osc = new OscInterface();
	GrooveInfo *gi = new GrooveInfo();
	
	
	//gi->currentGroove = currDummy++;
	//gi->predGroove = prevDummy++;
	
	//currDummy %= NUM_GROOVE_TEMPLATES + 1;
	//prevDummy %= NUM_GROOVE_TEMPLATES + 1;
	
	gi->currentGroove = Label[numMeasuresSofar];
	gi->predGroove = predMAPGroove;
	
	for (int i = 0; i < NUM_GROOVE_TEMPLATES; ++i) {
		gi->predDist[i] = grPredDist[i];
	}
	osc->sendOscData(gi);
}

// given a note, we find out who played it
int routeMidiMessage(int note, int& player, int& stroke)
{
	// set up midi notes for each player
	int midiNoteSpec[NUM_PLAYERS][NUM_STROKES];
	midiNoteSpec[0][0] = 36; // 
	midiNoteSpec[0][1] = 38; // 
	midiNoteSpec[0][2] = 40; //
	midiNoteSpec[0][3] = 41; //
	
	midiNoteSpec[1][0] = 48; // 
	midiNoteSpec[1][1] = 50; // 
	midiNoteSpec[1][2] = 52; //
	midiNoteSpec[1][3] = 53; //
	
	midiNoteSpec[2][0] = 60; // 
	midiNoteSpec[2][1] = 62; // 
	midiNoteSpec[2][2] = 64; //
	midiNoteSpec[2][3] = 65; //
	
	midiNoteSpec[3][0] = 72; // 
	midiNoteSpec[3][1] = 74; // 
	midiNoteSpec[3][2] = 76; //
	midiNoteSpec[3][3] = 77; //
	
	midiNoteSpec[4][0] = 84; // 
	midiNoteSpec[4][1] = 86; // 
	midiNoteSpec[4][2] = 88; // 
	midiNoteSpec[4][3] = 89; //
	
	player = -1;
	stroke = -1;
	for (int i=0; i<NUM_PLAYERS; i++) {
		for (int j=0; j<NUM_STROKES; j++) {
			if (midiNoteSpec[i][j] == note) {
				player = i;
				stroke = j;
			}
		}
	}
	
	if (player == -1 || stroke == -1) {
		std::cout << "Unknown stroke!" << std::endl;
		return -1;
	}
	return 0;
}

int main( int argc, char *argv[] )
{
	// prepare for file writing
	time_t rawtime;
	struct tm * timeinfo;
	
	time ( &rawtime );
	timeinfo = localtime ( &rawtime );
	//char *timerightnow = asctime (timeinfo);
	time_t timerightnow = time(NULL);
	char filenamexx[256];
	sprintf(filenamexx, "/Users/ruofeng/Groove-ProjStudio/%ld.txt", timerightnow);
	//sprintf(filenamexx, "/Users/ruofeng/Groove-ProjStudio/improv.txt");
	FILE * f_raw = fopen(filenamexx, "w");
	
	RtMidiIn *midiin = 0;
	std::vector<unsigned char> message;
	int nBytes;
	double stamp;
	
	// Minimal command-line check.
	if ( argc > 2 ) usage();
	
	
	// RtMidiIn constructor
	try {
		midiin = new RtMidiIn();
	}
	catch ( RtError &error ) {
		error.printMessage();
		exit( EXIT_FAILURE );
	}
	
	if (VIRTUAL == 0) {
		// Check available ports vs. specified.
		unsigned int port;
		unsigned int nPorts = midiin->getPortCount();
		if ( argc == 1 ) port = PORT;
		if ( argc == 2 ) port = (unsigned int) atoi( argv[1] );
		if ( port >= nPorts ) {
			delete midiin;
			std::cout << "Invalid port specifier!\n";
			usage();
		}
		
		try {
			midiin->openPort( port );
		}
		catch ( RtError &error ) {
			error.printMessage();
			//    goto cleanup;
		}
	}
	else {
		try {
			midiin->openVirtualPort();
		}
		catch ( RtError &error ) {
			error.printMessage();
			//    goto cleanup;
		}
	}
	
	// Don't ignore sysex, timing, or active sensing messages.
	midiin->ignoreTypes( true, true, true );
	
	// Install an interrupt handler function.
	done = false;
	(void) signal(SIGINT, finish);
	
	// midi out for metronome
	
	RtMidiOut *midiout = new RtMidiOut();
	unsigned char messageout[3];
	
	// Check available ports.
	unsigned int nPortsOut = midiout->getPortCount();
	if ( nPortsOut == 0 ) {
		std::cout << "No output ports available!\n";
		usage();
	}
	
	// Open first available port.
	//midiout->openVirtualPort();
	midiout->openPort();
	
	//---------- here begins the part that we care about -----------//	
	
	int sp = 20; // tick period
	int nread = measure_period / sp; // number of tick periods in a measure period
	int cntr = 0; // tick counter
	
	clear_arrays();
	int midi_cntr[NUM_PLAYERS]; // each player has a counter
	for (int i=0; i<NUM_PLAYERS; i++) {
		midi_cntr[i] = 0;
	}
	
	// Added by Ajay: Groove recognition
	char fstr[MAX_PATH_LENGTH];
	char *fileList[] = {"01.txt","02.txt","03.txt","04.txt","05.txt","06.txt","07.txt","08.txt","09.txt","10.txt","11.txt","12.txt","13.txt","14.txt","15.txt","16.txt","17.txt","18.txt","19.txt"};
	char *pstr = "/Users/ruofeng/Groove-ProjStudio/GrooveTemplates/GroovesActual/Groove";
	int temp1, temp2, temp3;
	FILE *fp;
	int k = 0;
	for(int j = 0;j<NUM_GROOVE_TEMPLATES;j++){
		strcpy(fstr,pstr);
		fp = fopen(strcat(fstr,fileList[j]),"rt");
		puts(fstr);
		if(!fp)
			printf("File Not Found\n");
		else{
			while(!feof(fp)){
				fscanf(fp,"%d %d %d\n", &temp1, &temp2, &temp3);
				gTemplates[j].note[k] = (char)(getNoteName(temp1));
				gTemplates[j].dur[k] = (char)(getDurName(temp2));
				k++;
			}
			gTemplates[j].length = k;
			gTemplates[j].note[k] = '\0';
			gTemplates[j].dur[k] = '\0';
			k = 0;
		}
		fclose(fp);
	}
	/*End of reading groove templates*/
	
	PST = createTree();
	
	// for false notes filtering
	int detect_switch = 1; // 1 means we accept onsets
	
	/*End of initilaizing the PSTs*/
	// Periodically check input queue.
	std::cout << "Reading MIDI from port ... quit with Ctrl-C.\n";
	while ( !done ) {		
		stamp = midiin->getMessage( &message );
		nBytes = message.size();
		
		// NOTE: FOR THE NEW DEVICE, message[0] is not 144!!!!!!!!!!!!!
		if (nBytes > 2 && (message[0] == 144 || message[0] == 153) && message[2] > 0) {
			// indicates a note keydown: message[1] is midi note; message[2] is velocity
			int midinote = (int)message[1];
			double position = (double)cntr/nread;
			int velocity = (int)message[2];
//			std::cout << "midi " << midinote << "\tvelocity " << velocity <<"\ttime " << position << std::endl;
			
			int player = -1, stroke = -1;
			if (routeMidiMessage(message[1], player, stroke) == 0) {
				// based on the midi note, we can figure out where it comes from
				
				// search the current read midi in the tables to check false onset
				for (int i=midi_cntr[player]-1; i>=0; i--) {
					if (midinotes[player][i] == midinote && positions[player][i] + MAX_IOI > position) {
						detect_switch = 0;
						break;
					}
				}
				
				// only this will be added for analysis
				if (detect_switch == 1) {
					std::cout << "midi " << midinote << "\tvelocity " << velocity <<"\ttime " << position << std::endl;
					
					midinotes[player][midi_cntr[player]] = midinote;
					positions[player][midi_cntr[player]] = position;
					velocities[player][midi_cntr[player]] = velocity;
					
					midi_cntr[player] += 1;
				}
				detect_switch = 1;
				
				if (midi_cntr[player] == MAXNUM_MIDI) {
					std::cout << "number of midi notes exceed defined value!" << std::endl;
				}
			}
		}
		
		// reach the end of the measure
		if (cntr==nread) {
			//measure_end(f_raw); // do some analysis here!
			measure_end_FAKE();
			clear_arrays();
			
			for (int i=0; i<NUM_PLAYERS; i++) {
				midi_cntr[i] = 0;
			}
			cntr = 0;
		}

#define DELAY -1
		
		// metronome at beats
		if (cntr == -DELAY) {
			messageout[0] = 144;
			messageout[1] = 75;
			messageout[2] = 90;
			std::vector<unsigned char> v(messageout, messageout + 3);
			midiout->sendMessage( &v );
		}
		if (cntr == 4-DELAY) {
			messageout[0] = 128;
			messageout[1] = 75;
			messageout[2] = 40;
			std::vector<unsigned char> v(messageout, messageout + 3);
			midiout->sendMessage( &v );			
		}
		
		// metronome at sub-beats
		if (cntr == nread/4-DELAY || cntr == nread/2-DELAY || cntr == nread/4*3-DELAY) {
			messageout[0] = 144;
			messageout[1] = 80;
			messageout[2] = 90;
			std::vector<unsigned char> v(messageout, messageout + 3);
			midiout->sendMessage( &v );
		}
		if (cntr == nread/4+4-DELAY || cntr == nread/2+4-DELAY || cntr == nread/4*3+4-DELAY) {
			messageout[0] = 128;
			messageout[1] = 80;
			messageout[2] = 40;
			std::vector<unsigned char> v(messageout, messageout + 3);
			midiout->sendMessage( &v );			
		}
		cntr++;
		SLEEP( sp );
	}
	
	// Clean up
	//	cleanup:
	delete midiin;
	delete midiout;
	fclose(f_raw);
	
	return 0;
}